﻿
using System;
using System.Net;
using System.Net.Sockets;

namespace GridStatLite
{
    sealed class UdpReceiver
    {
        // *************************************************************** //
        #region [ member fields ]

        private Socket m_socket;
        private SocketAsyncEventArgs m_asyncEvtArgs;

        private byte[] m_recvBuffer;
        private EndPoint m_srcEndpoint;// making it member variable for reuse

        /// <summary>
        /// return true indicating receiving next, otherwise stop receiving next
        /// </summary>
        private readonly Func<IPEndPoint, byte[], int, int, bool> RecvedCallback;
        private readonly Action<SocketAsyncOperation, SocketError> ErrorCallback;

        #endregion

        // *************************************************************** //
        #region [ constructor ]

        /// <summary>
        /// the parameter socket and EventArgs are delegated to this class
        /// so this class itself don't need to manage their life cycle
        /// </summary>
        public UdpReceiver(Socket socket, SocketAsyncEventArgs evtArgs,
            Func<IPEndPoint, byte[], int, int, bool> recvedCallback,
            Action<SocketAsyncOperation, SocketError> errorCallback)
        {
            m_socket = socket;

            m_asyncEvtArgs = evtArgs;
            m_asyncEvtArgs.Completed += this.OnReceivedCallback;

            RecvedCallback = recvedCallback;
            ErrorCallback = errorCallback;

            m_recvBuffer = new byte[1024];
            m_srcEndpoint = new IPEndPoint(IPAddress.Any, 0);// output parameter used with "ref"
        }

        #endregion

        // *************************************************************** //
        #region [ public APIs ]

        public void StartReceive()
        {
            int threadid = System.Threading.Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine("******************** start receive, threadid={0}",threadid);

            m_asyncEvtArgs.SetBuffer(m_recvBuffer, 0, m_recvBuffer.Length);
            m_asyncEvtArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 7027);

            if (!m_socket.ReceiveFromAsync(m_asyncEvtArgs))
            {
                Console.WriteLine("!!!!!!!!!!!!!! <threadid={0}>,sync receive completed.",threadid);
                OnReceivedCallback(this, m_asyncEvtArgs);
            }
        }

        #endregion

        // *************************************************************** //
        #region [ private helplers ]

        private void OnReceivedCallback(object sender, SocketAsyncEventArgs evtargs)
        {
            if (evtargs.LastOperation == SocketAsyncOperation.ReceiveFrom)
            {
                if (evtargs.SocketError == SocketError.Success && evtargs.BytesTransferred > 0)
                {
                    IPEndPoint srcEndpoint = evtargs.RemoteEndPoint as IPEndPoint;

                    if (srcEndpoint == null)
                        Console.WriteLine("!!!!!! null source endpoint");
                    else if (srcEndpoint.Port != 7027)
                    {


                        if (RecvedCallback(srcEndpoint, evtargs.Buffer, evtargs.Offset, evtargs.BytesTransferred))
                            StartReceive();

                    }
                    else
                        Console.WriteLine("\t\t\t!!!!!!! invalid source: {0}", srcEndpoint);
                }
                else
                {
                    throw new SocketException((int)evtargs.SocketError);
                    ErrorCallback(evtargs.LastOperation, evtargs.SocketError);
                }
            }
        }

        #endregion
    }
}
